#include "c3DisplayObject.h"
#include "c3Mode.h"

bool DisplayObject::mDynamicFlag = false;

DisplayObject::DisplayObject()
{
	mIsDynamic = mDynamicFlag;
	mDynamicFlag = false;

  mChildren.reserve(32);

  Alpha = 255;
  Rotation = 0;
  Color = 0xffffffff;
  X = Y = 0;
  Width = Height = 0;
	BlendMode = BM_ALPHA;
	ScaleX = ScaleY = 1.0f;
  Visible = true;
  MouseChildren = Enabled = true;
  Zombie = false;

  NextThink = 0;

  InputMask = false;

  Userdata[0] = 0;
  Userdata[1] = 0;
  Userdata[2] = 0;
  Userdata[3] = 0;

  mFlags = 0;

  //MouseX = MouseY = 0;

  TheImage = null;

  mParent = null;

  mEvents = null;

  Animate.mParent = this;
}

DisplayObject::~DisplayObject()
{
	DestroyChildren();
}

void DisplayObject::DestroyChildren()
{
	for (size_t i=0; i<mChildren.size(); i++)
	{
		mChildren[i]->DestroyChildren();
		if (mChildren[i]->mIsDynamic)
		{
			delete mChildren[i];
		}
	}
	mChildren.clear();
}

void DisplayObject::AddEvent(int event, DisplayObject* control)
{
  if (control == 0) control = this;

  if (mEvents)
  {
    mEvents->PushEvent(event, control);
  } else if (mParent)
  {
    mParent->AddEvent(event, control);
  }
}

int DisplayObject::GetChildIndex(const DisplayObject* obj) const
{
  if (obj->GetParent() != this) return -1;

  for (uint i=0; i<mChildren.size(); i++)
  {
    if (mChildren[i] == obj)
      return i;
  }

  return -1;
}

DisplayObject* DisplayObject::GetChildAtPoint(int x, int y, bool onlyEnabled)
{
  if (onlyEnabled && Enabled == false) return null;

  if (!onlyEnabled || (onlyEnabled && MouseChildren))
  {
    for (int i=int(mChildren.size())-1; i>=0; --i)
    {
      DisplayObject* child = mChildren[i]->GetChildAtPoint(x-X, y-Y, onlyEnabled);
      if (child) return child;
    }
  }

  if (!IsPointInside(x,y,true)) return null;

  return this;
}

int DisplayObject::AddChild(DisplayObject& obj)
{
  return AddChild(&obj);
}

int DisplayObject::AddChild(DisplayObject* obj)
{
  if (!obj) return -1;
  if (obj->GetParent() == this) return GetChildIndex(obj);

  if (obj->GetParent())
    obj->GetParent()->RemoveChild(obj);
  mChildren.push_back(obj);

  obj->Attached(this);

  return int(mChildren.size())-1;
}

int DisplayObject::AddToParent(DisplayObject& obj)
{
  return obj.AddChild(this);
}
int DisplayObject::AddToParent(DisplayObject* obj)
{
  return obj->AddChild(this);
}

DisplayObject* DisplayObject::RemoveChildAt(uint at)
{
  vector<DisplayObject*>::iterator iter = mChildren.begin()+at;
  if (iter != mChildren.end())
  {
    DisplayObject* obj = *iter;
    obj->Unattached();
    mChildren.erase(iter);
    return obj;
  } else
  {
    return 0;
  }
}

DisplayObject* DisplayObject::RemoveChild(DisplayObject* obj)
{
  if (obj->GetParent() != this) return 0;

  for (vector<DisplayObject*>::iterator iter = mChildren.begin(); iter!=mChildren.end(); ++iter)
  {
    if (*iter == obj)
    {
      obj->Unattached();
      mChildren.erase(iter);
      return obj;
    }
  }

  return 0;
}

void DisplayObject::Attached(DisplayObject* parent)
{
  mParent = parent;
  //obj->mEvents = parent->mEvents;
}

void DisplayObject::Unattached()
{
  mParent = null;
  //obj->mEvents = null;
}

uint DisplayObject::Update(int dt)
{
  mAnimation.Do(dt, this);

  if (NextThink)
  {
    NextThink -= dt;
    if (NextThink <= 0)
    {
      NextThink = 0;
      Think();
    }
  }

  UpdateAt(dt);

  size_t numChildren = mChildren.size();
  for (uint i=0; i<numChildren; i++)
  {
    mChildren[i]->Update(dt);
  }

  return 0;
}

uint DisplayObject::Draw(int x, int y, uint8 alpha)
{
  if (!Visible) return 0;

  DrawAt(X+x, Y+y, alpha);

  uint8 newAlpha = (int(alpha)*int(Alpha))/255;
  size_t numChildren = mChildren.size();
  for (uint i=0; i<numChildren; i++)
  {
    mChildren[i]->Draw(X+x, Y+y, newAlpha);
  }

  return 0;
}

void DisplayObject::UpdateAt(int dt)
{
}

uint DisplayObject::DrawAt(int x, int y, uint8 alpha)
{
  int outcolor = Color;
  if (Effects.ColorFilter.Enabled)
  {
    if (Effects.ColorFilter.Mode == __Effects::ColorFilterEffect::Multiply)
    {
      int ecolor = Effects.ColorFilter.Color;
      int finalColor = MultiplyRGBA(outcolor, ecolor);

      outcolor = finalColor;
    } else if (Effects.ColorFilter.Mode == __Effects::ColorFilterEffect::Add)
    {
      /// TODO: fix this, or delete it
      int ecolor = Effects.ColorFilter.Color;
      int finalColor = 0;
      finalColor |= min(255, (outcolor&255)+(ecolor&255));
      outcolor >>= 8;
      ecolor >>= 8;
      finalColor |= (min(255, (outcolor&255)+(ecolor&255)))<<8;
      outcolor >>= 8;
      ecolor >>= 8;
      finalColor |= (min(255, (outcolor&255)+(ecolor&255)))<<16;
      outcolor >>= 8;
      ecolor >>= 8;
      finalColor |= (min(255, (outcolor&255)+(ecolor&255)))<<24;
      outcolor >>= 8;
      ecolor >>= 8;

      outcolor = finalColor;
    }
  }
  int outalpha = (int(Alpha)*int(alpha))/255;
  if (TheImage) TheImage->saypx(x, y, 0, 0, -30000, -30000, uint8(outalpha), Rotation, outcolor, BlendMode, 2048, 2048, int(ScaleX*4096.0f), int(ScaleY*4096.0f));
	if (Effects.Glow.Enabled)
		DrawQuoteUnquoteGlow(TheImage, x, y, Effects.Glow.Color);
  return 0;
}

void DisplayObject::AlignToParent(int horizontal, int vertical)
{
  if (!mParent) return;
  if ((Width == 0 || Height == 0) && TheImage == 0) return;
  int pw = (mParent->Width==0)?mParent->TheImage->dx:mParent->Width;
  int ph = (mParent->Height==0)?mParent->TheImage->dy:mParent->Height;
  int w = (Width==0)?TheImage->dx:Width;
  int h = (Height==0)?TheImage->dy:Height;
  if (horizontal == Align::Left)
    X = 0;
  else if (horizontal == Align::Center)
    X = pw/2-w/2;
  else if (horizontal == Align::Right)
    X = pw-w;

  if (vertical == Align::Top)
    Y = 0;
  else if (vertical == Align::Center)
    Y = ph/2-h/2;
  else if (vertical == Align::Bottom)
    Y = ph-h;
}

bool DisplayObject::IsPointInside( int x, int y, bool applyInputMask) const
{
  if (!applyInputMask || (applyInputMask && !InputMask))
    return (x>=X && y>=Y && x<=int(X+Width) && y<=int(Y+Height));
  else
    return InputMaskRect.IsInside(x,y);
}

bool DisplayObject::IsOverlapping( const DisplayObject& other ) const
{
  return (
    (X <= other.X+other.Width)&&(X+Width >= other.X)&&
    (Y <= other.Y+other.Height)&&(Y+Height >= other.Y)
  );
}

c3Rectangle DisplayObject::GetRect() const
{
  return c3Rectangle(X,Y,X+Width,Y+Height);
}

void DisplayObject::SetDimsFromImage(Image* fromImage)
{
  Image* img = fromImage;
  if (!img) img = TheImage;
  if (!img) return;

  Width = img->dx;
  Height = img->dy;

  if (!InputMask) InputMaskRect = GetRect();
}

c3Point DisplayObject::LocalToGlobal(int x, int y)
{
  if (mParent)
  {
    return mParent->LocalToGlobal(X+x, Y+y);
  } else
  {
    return c3Point(X+x, Y+y);
  }
}

void DisplayObject::SetSize( int width, int height )
{
	Width = width;
	Height = height;
}

void DisplayObject::AlignChildren( int horizontal, int vertical /*= Align::NoAlign*/ )
{
	if (!mParent) return;
	for (int i=0; i<mChildren.size(); i++)
	{
		mChildren[i]->AlignToParent(horizontal, vertical);
	}
}

void DisplayObject::SetAbsolutePosition( int x, int y )
{
	DisplayObject* parent = this;
	
	X = Y = 0;

	int dx = 0, dy = 0;
	while (parent = parent->mParent)
	{
		dx += parent->X;
		dy += parent->Y;
	}
	X = -dx + x;
	Y = -dy + y;
}

void DisplayObject::SetPosition( int x, int y )
{
	X = x;
	Y = y;
}

void DisplayObject::SetSizeFromChildren( bool expandToNegative /*= true*/ )
{
	c3Rectangle curRect = GetRect().Translated(-X, -Y);
	c3Rectangle oldRect = curRect;
	for (size_t i = 0; i<mChildren.size(); i++)
	{
		DisplayObject* child = mChildren[i];
		
		curRect.Include(child->GetRect());
	}

	if (expandToNegative)
	{
		int difX = curRect.X1 - oldRect.X1;
		int difY = curRect.Y1 - oldRect.Y1;

		curRect.X1 = oldRect.X1;
		curRect.Y1 = oldRect.Y1;

		Width = curRect.GetWidth();
		Height = curRect.GetHeight();

		if (difX < 0)
		{
			X += difX;
			Width += -difX;
			for (size_t i = 0; i<mChildren.size(); i++)
				mChildren[i]->MoveBy(-difX, 0);
		}
		if (difY < 0)
		{
			Y += difY;
			Height += -difY;
			for (size_t i = 0; i<mChildren.size(); i++)
				mChildren[i]->MoveBy(0, -difY);
		}
	} else
	{
		Width = curRect.GetWidth();
		Height = curRect.GetHeight();
	}
}

void DisplayObject::MoveBy( int x, int y )
{
	X += x;
	Y += y;
}

void DisplayObject::_Animate::FadeIn( int speed /*= 1*/ )
{
	if (!mParent->Visible)
	{
		mParent->Visible = true;
		mParent->Alpha = 0;
	}
  mParent->mAnimation.AlphaFade.mEnabled = true;
  mParent->mAnimation.AlphaFade.mDestAlpha = 255;
  mParent->mAnimation.AlphaFade.mFadeSpeed = speed;
}

void DisplayObject::_Animate::FadeOut( int speed /*= 1*/ )
{
  mParent->mAnimation.AlphaFade.mEnabled = true;
  mParent->mAnimation.AlphaFade.mDestAlpha = 0;
  mParent->mAnimation.AlphaFade.mFadeSpeed = speed;
}

void DisplayObject::_Animate::SlideFrom( int x, int y, int intime, int delay )
{
	mParent->mAnimation.Slide.mEnabled = true;
	mParent->mAnimation.Slide.mFromX = x;
	mParent->mAnimation.Slide.mFromY = y;
	mParent->mAnimation.Slide.mToX = mParent->X;
	mParent->mAnimation.Slide.mToY = mParent->Y;
	mParent->mAnimation.Slide.mTime = -delay;
	mParent->mAnimation.Slide.mEndTime = intime;
}

void DisplayObject::_Animate::Pulse( int inTime /*= 60*/, float scaleX /*= 0.2f*/, float scaleY /*= 0.2f*/ )
{
	if (inTime)
	{
		mParent->mAnimation.Pulse.mEnabled = true;
		mParent->mAnimation.Pulse.mTime = 0;
		mParent->mAnimation.Pulse.mSpeed = inTime;
		mParent->mAnimation.Pulse.mScaleX = scaleX;
		mParent->mAnimation.Pulse.mScaleY = scaleY;
	} else
	{
		mParent->mAnimation.Pulse.mEnabled = false;
		mParent->ScaleX = mParent->ScaleY = 1.0f;
	}
}

void DisplayObject::_Animate::ShowAfter( int frames )
{
	mParent->mAnimation.ShowAfter.mEnabled = true;
	mParent->mAnimation.ShowAfter.mShow = true;
	mParent->mAnimation.ShowAfter.mTimeLeft = frames;
}

void DisplayObject::_Animate::HideAfter( int frames )
{
	mParent->mAnimation.ShowAfter.mEnabled = true;
	mParent->mAnimation.ShowAfter.mShow = false;
	mParent->mAnimation.ShowAfter.mTimeLeft = frames;
}

void DisplayObject::_Animate::Bounce( int inTime /*= 60*/, int height /*= 50*/ )
{
	if (mParent->mAnimation.Bounce.mEnabled)
		mParent->Y = mParent->mAnimation.Bounce.mBaseY;
	if (inTime)
	{
		mParent->mAnimation.Bounce.mEnabled = true;
		mParent->mAnimation.Bounce.mInTime = inTime;
		mParent->mAnimation.Bounce.mHeight = height;
		mParent->mAnimation.Bounce.mTime = 0;
		mParent->mAnimation.Bounce.mBaseY = mParent->Y;
	} else
	{
		mParent->mAnimation.Bounce.mEnabled = false;
	}
}

void DisplayObject::AnimationStruct::AlphaFadeStruct::Do(int dt, DisplayObject* on)
{
  if (mDestAlpha < on->Alpha)
  {
    int newAlpha = int(on->Alpha) - dt * mFadeSpeed;
    if (mDestAlpha > newAlpha)
    {
      newAlpha = mDestAlpha;
      mEnabled = false;
    }

    on->Visible = (newAlpha != 0);

    on->Alpha = uint8(newAlpha);

  } else if (mDestAlpha > on->Alpha)
  {
    int newAlpha = int(on->Alpha) + dt * mFadeSpeed;
    if (mDestAlpha < newAlpha)
    {
      newAlpha = mDestAlpha;
      mEnabled = false;
    }

    on->Visible = (newAlpha != 0);

    on->Alpha = uint8(newAlpha);
  } else
  {
    mEnabled = false;
  }
}

inline float SmoothInterpolate(float start, float end, float val) {
	return start + ((end - start) * pow(val,0.333f))/*(val*val*(3.0f-2.0f*val)))*/;
}

void DisplayObject::AnimationStruct::SlideStruct::Do( int dt, DisplayObject* on )
{
	mTime += dt;
	float t = max(0.0f, min(1.0f, float(mTime)/float(mEndTime)));
	float x = SmoothInterpolate(mFromX, mToX, t);
	float y = SmoothInterpolate(mFromY, mToY, t);
	on->X = (int)x;
	on->Y = (int)y;

	if (mTime >= mEndTime)
		mEnabled = false;
}

void DisplayObject::AnimationStruct::PulseStruct::Do( int dt, DisplayObject* on )
{
	mTime += dt;
	float t = (float(mTime%mSpeed)/mSpeed)*3.1415926f*2.0f;
	float sint = sin(t);
	on->ScaleX = 1.0f + sint*mScaleX;
	on->ScaleY = 1.0f + sint*mScaleY;
}

void DisplayObject::AnimationStruct::ShowAfterStruct::Do( int dt, DisplayObject* on )
{
	mTimeLeft -= dt;
	if (mTimeLeft <= 0)
	{
		if (mShow)
			on->Show();
		else 
			on->Hide();

		mEnabled = false;
	}
}

void DisplayObject::AnimationStruct::BounceStruct::Do( int dt, DisplayObject* on )
{
	mTime += dt;
	float percent = sin((float(mTime%mInTime)/float(mInTime)) * 3.1415926F);
	on->Y = mBaseY - int(percent*float(mHeight));
}